﻿using System;
using System.Collections.Generic;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;

namespace DatabaseGroup.SSIS.SourceAdapters
{
    /// <summary>
    /// Delegate for firing an error message to SSIS
    /// </summary>
    /// <param name="errorMessage">The error message to fire</param>
    public delegate void PostErrorDelegate(string errorMessage);

    /// <summary>
    /// Handles setting up and validating all the component custom properties
    /// </summary>
    public class PropertiesManager
    {
        /// <summary>
        /// Event for when an error needs to be fired to SSIS
        /// </summary>
        public event PostErrorDelegate PostErrorEvent = null;

        /// <summary>
        /// Delegate for confirming a property can use a specific value
        /// </summary>
        /// <param name="propertyName">The name of the property to validate</param>
        /// <param name="propertyValue">The value to validate</param>
        /// <returns>The status of the property validation</returns>
        public delegate DTSValidationStatus ValidateProperty(string propertyName, object propertyValue);

        #region Constants

        // TODO: consider converting to readonly properties
        public const string IsUnicodePropName = "IsUnicode";
        public const string CodePagePropName = "CodePage";
        public const string DelayValidationPropName = "DelayValidation";
        public const string TextQualifierPropName = "TextQualifier";
        public const string HeaderRowDelimiterPropName = "HeaderRowDelimiter";
        public const string HeaderRowsToSkipPropName = "HeaderRowsToSkip";
        public const string DataRowsToSkipPropName = "DataRowsToSkip";
        public const string ColumnNamesInFirstRowPropName = "ColumnNamesInFirstRow";
        public const string ColumnDelimiterPropName = "ColumnDelimiter";
        public const string TreatEmptyStringsAsNullPropName = "TreatEmptyStringsAsNull";
        public const string RowDelimiterPropName = "RowDelimiter";
        public const string TrimWhitespacePropName = "TrimWhitespace";
        public const string EscapeCharPropName = "EscapeChar";
        public const string IncludeErrorAuditRowsPropName = "IncludeErrorAuditRows";
        public const string IncludeErrorValidationMessagesPropName = "IncludeErrorValidationMessages";
        public const string IncludeErrorSourceDataPropName = "IncludeErrorSourceData";
        public const string ErrorSourceDataTypePropName = "ErrorSourceDataType";
        public const string ErrorSourceDataSizePropName = "ErrorSourceDataSize";
        public const string IncludeLogOutputPropName = "IncludeLogOutput";
        public const string IncludeValidationMessagesPropName = "IncludeValidationMessages";
        public const string IncludeLogSourceDataPropName = "IncludeLogSourceData";
        public const string LogSourceDataTypePropName = "LogSourceDataType";
        public const string LogSourceDataSizePropName = "LogSourceDataSize";
        public const string ConfiguredFileColumnsPropName = "ConfiguredFileColumns";

        public const int DefaultCodePage = 1252;
        public const int DefaultLocale = 1032;

        private const string DefaultDelimiter = ",";
        private const string DefaultRowDelimiter = "\r\n";
        private const char DefaultEscapeChar = '"';
        private const int DefaultLogSourceDataSize = 255;
        private const int MaxDelimLength = 20;

        private const string NonBrowsableCustomProperty = "NOTBROWSABLE";

        #endregion

        private readonly Dictionary<string, ValidateProperty> _propertyValidationTable = new Dictionary<string, ValidateProperty>();

        /// <summary>
        /// Initializes a new instance of PropertiesManager and
        /// sets up the column-to-validation list
        /// </summary>
        public PropertiesManager()
        {
            SetPropertyValidationTable();
        }

        /// <summary>
        /// Adds all the custom properties the component requires
        /// </summary>
        /// <param name="propertyCollection">The custom property collection to add the properties to</param>
        public static void AddComponentProperties(IDTSCustomPropertyCollection100 propertyCollection)
        {
            AddCustomProperty(propertyCollection, IsUnicodePropName, 
                MessageStrings.IsUnicodePropDescription, false);
            AddCustomProperty(propertyCollection, DelayValidationPropName,
                MessageStrings.DelayValidationPropDescription, false);
            AddCustomProperty(propertyCollection, TreatEmptyStringsAsNullPropName, 
                MessageStrings.TreatEmptyStringsAsNullDescription, true);
            AddCustomProperty(propertyCollection, CodePagePropName, 
                MessageStrings.CodePagePropDescription, DefaultCodePage);
            AddCustomProperty(propertyCollection, TextQualifierPropName, 
                MessageStrings.TextQualifierPropDescription, string.Empty, typeof(DelimiterStringConverter).AssemblyQualifiedName);
            AddCustomProperty(propertyCollection, HeaderRowDelimiterPropName, 
                MessageStrings.HeaderRowDelimiterPropDescription, DefaultRowDelimiter, typeof(DelimiterStringConverter).AssemblyQualifiedName);
            AddCustomProperty(propertyCollection, HeaderRowsToSkipPropName, 
                MessageStrings.HeaderRowsToSkipPropDescription, 0);
            AddCustomProperty(propertyCollection, DataRowsToSkipPropName, 
                MessageStrings.DataRowsToSkipPropDescription, 0);
            AddCustomProperty(propertyCollection, ColumnNamesInFirstRowPropName,
                MessageStrings.ColumnNameInFirstRowPropDescription, false, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, ColumnDelimiterPropName, 
                MessageStrings.ColumnDelimiterPropDescription, DefaultDelimiter, typeof(DelimiterStringConverter).AssemblyQualifiedName);
            AddCustomProperty(propertyCollection, RowDelimiterPropName, 
                MessageStrings.RowDelimiterPropDescription, DefaultRowDelimiter, typeof(DelimiterStringConverter).AssemblyQualifiedName);
            AddCustomProperty(propertyCollection, TrimWhitespacePropName, 
                MessageStrings.TrimWhitespacePropDescription, false);
            AddCustomProperty(propertyCollection, EscapeCharPropName, 
                MessageStrings.EscapeCharPropDescription, DefaultEscapeChar);
            AddCustomProperty(propertyCollection, IncludeErrorAuditRowsPropName,
                MessageStrings.IncludeErrorAuditRowsPropDescription, true, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, IncludeErrorValidationMessagesPropName,
                MessageStrings.IncludeErrorValidationMessagesPropDescription, true, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, IncludeErrorSourceDataPropName,
                MessageStrings.IncludeErrorSourceDataPropDescription, true, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, ErrorSourceDataTypePropName,
                MessageStrings.ErrorSourceDataTypePropDescription, DataType.DT_STR, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, ErrorSourceDataSizePropName,
                MessageStrings.ErrorSourceDataSizePropDescription, DefaultLogSourceDataSize, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, IncludeLogOutputPropName,
                MessageStrings.IncludeLogOutputPropDescription, true, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, IncludeValidationMessagesPropName,
                MessageStrings.IncludeValidationMessagesPropDescription, true, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, IncludeLogSourceDataPropName,
                MessageStrings.IncludeLogSourceDataPropDescription, true, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, LogSourceDataTypePropName,
                MessageStrings.LogSourceDataTypePropDescription, DataType.DT_STR, NonBrowsableCustomProperty);
            AddCustomProperty(propertyCollection, LogSourceDataSizePropName,
                MessageStrings.LogSourceDataSizePropDescription, DefaultLogSourceDataSize, NonBrowsableCustomProperty);

            IDTSCustomProperty100 property = 
                AddCustomProperty(propertyCollection, ConfiguredFileColumnsPropName, 
                    MessageStrings.ConfiguredFileColumnsPropDescription,
                    CommonUtils.SerializeConfiguredFileColumns(new List<ConfiguredFileColumn>()), NonBrowsableCustomProperty);

            // wrap the property within a CDATA section when the package is saved to XML
            property.State = DTSPersistState.PS_PERSISTASCDATA;
        }
        
        /// <summary>
        /// Gets the value of a specific custom property from the custom
        /// properties collection
        /// </summary>
        /// <param name="propertyCollection">The custom properties collection to search</param>
        /// <param name="name">The name of the custom property to search for</param>
        /// <returns>The value of the custom property</returns>
        public static T GetPropertyValue<T>(IDTSCustomPropertyCollection100 propertyCollection, string name)
        {
            IDTSCustomProperty100 property = propertyCollection[name];

            // TODO: MessageStrings
            if (property == null)
                throw new ArgumentException("Failed to find custom property matching the name provided", "name"); 

            return (T)property.Value;
        }

        /// <summary>
        /// Validates all custom properties in the custom property collection
        /// </summary>
        /// <param name="customPropertyCollection">The collection of custom properties to validate</param>
        /// <param name="oldStatus">The old validation status</param>
        /// <returns>The new validation status based on validating all custom properties</returns>
        public DTSValidationStatus ValidateProperties(IDTSCustomPropertyCollection100 customPropertyCollection, DTSValidationStatus oldStatus)
        {
            DTSValidationStatus resultStatus = oldStatus;
            foreach (IDTSCustomProperty100 property in customPropertyCollection)
            {
                resultStatus = ValidatePropertyValue(property.Name, property.Value, resultStatus);
            }

            return resultStatus;
        }

        /// <summary>
        /// Validates a column based on its associated validation method
        /// defined within the internal column-to-validation table
        /// </summary>
        /// <param name="propertyName">The name of the custom property to validate</param>
        /// <param name="propertyValue">The value of the custom property</param>
        /// <param name="oldStatus">The old validation status</param>
        /// <returns>The validation status based on the old status and the current property validation</returns>
        public DTSValidationStatus ValidatePropertyValue(string propertyName, object propertyValue, DTSValidationStatus oldStatus)
        {
            DTSValidationStatus resultStatus = oldStatus;
            if (_propertyValidationTable.ContainsKey(propertyName))
            {
                resultStatus = CommonUtils.CompareValidationValues(resultStatus, _propertyValidationTable[propertyName](propertyName, propertyValue));
            }
            return resultStatus;
        }

        /// <summary>
        /// Validates a boolean related custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="propertyValue">The value of the custom property</param>
        /// <returns>The result of the validation</returns>
        public DTSValidationStatus ValidateBooleanProperty(string propertyName, object propertyValue)
        {
            if (propertyValue is bool)
                return DTSValidationStatus.VS_ISVALID;

            PostError(MessageStrings.InvalidPropertyValue(propertyName, propertyValue));
            return DTSValidationStatus.VS_ISCORRUPT;
        }

        /// <summary>
        /// Validates a char related custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="propertyValue">The value of the custom property (char or ushort)</param>
        /// <returns>The result of the validation</returns>
        public DTSValidationStatus ValidateCharProperty(string propertyName, object propertyValue)
        {
            if (propertyValue is char || propertyValue is ushort)
                return DTSValidationStatus.VS_ISVALID;

            PostError(MessageStrings.InvalidPropertyValue(propertyName, propertyValue));
            return DTSValidationStatus.VS_ISCORRUPT;
        }

        /// <summary>
        /// Validates a string DataType related custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="propertyValue">The value of the custom property</param>
        /// <returns>The result of the validation</returns>
        public DTSValidationStatus ValidateStringDataTypeProperty(string propertyName, object propertyValue)
        {
            if (propertyValue is DataType || propertyValue is int)
            {
                switch ((DataType)propertyValue)
                {
                    case DataType.DT_NTEXT:
                    case DataType.DT_STR:
                    case DataType.DT_WSTR:
                    case DataType.DT_TEXT:
                        return DTSValidationStatus.VS_ISVALID;
                    default:
                        PostError(MessageStrings.InvalidPropertyDataType(propertyName, propertyValue.ToString()));
                        return DTSValidationStatus.VS_ISBROKEN;
                }
            }

            PostError(MessageStrings.InvalidPropertyValue(propertyName, propertyValue));
            return DTSValidationStatus.VS_ISCORRUPT;
        }

        /// <summary>
        /// Validates a delimiter related custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="propertyValue">The value of the custom property</param>
        /// <returns>The result of the validation</returns>
        public DTSValidationStatus ValidateDelimiterProperty(string propertyName, object propertyValue)
        {
            if (propertyValue is string)
            {
                string value = (string)propertyValue;
                if (value.Length < MaxDelimLength)
                    return DTSValidationStatus.VS_ISVALID;

                PostError(MessageStrings.PropertyStringTooLong(propertyName, propertyValue.ToString()));
                return DTSValidationStatus.VS_ISBROKEN;
            }

            PostError(MessageStrings.InvalidPropertyValue(propertyName, propertyValue));
            return DTSValidationStatus.VS_ISCORRUPT;
        }

        /// <summary>
        /// Validates a row delimiter related custom property
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="propertyValue">The value of the custom property</param>
        /// <returns>The result of the validation</returns>
        public DTSValidationStatus ValidateRowDelimiterProperty(string propertyName, object propertyValue)
        {
            DTSValidationStatus retStatus = ValidateDelimiterProperty(propertyName, propertyValue);
            if (retStatus == DTSValidationStatus.VS_ISVALID)
            {
                string value = (string)propertyValue;
                if (value.Length == 0)
                {
                    PostError(MessageStrings.RowDelimiterEmpty);
                    retStatus = DTSValidationStatus.VS_ISBROKEN;
                }
            }

            return retStatus;
        }

        /// <summary>
        /// Validates the configured columns custom property,
        /// ensuring that it is a valid XML block of file column configurations
        /// </summary>
        /// <param name="propertyName">The name of the custom property</param>
        /// <param name="propertyValue">The value of the custom property</param>
        /// <returns>The result of the validation</returns>
        public DTSValidationStatus ValidateConfiguredFileColumnsProperty(string propertyName, object propertyValue)
        {
            try
            {
                CommonUtils.DeserializeConfiguredFileColumns((string)propertyValue);
                return DTSValidationStatus.VS_ISVALID;
            }
            catch
            {
                return DTSValidationStatus.VS_ISCORRUPT;
            }
        }

        /// <summary>
        /// Adds a custom property to a property collection
        /// </summary>
        /// <param name="propertyCollection">The property collection to add the property to</param>
        /// <param name="name">The name of the custom property to add</param>
        /// <param name="description">The description of the custom property to add</param>
        /// <param name="defaultValue">The value of the custom property to add</param>
        /// <returns>The added custom property object</returns>
        private static IDTSCustomProperty100 AddCustomProperty(IDTSCustomPropertyCollection100 propertyCollection, string name, string description, object defaultValue)
        {
            return AddCustomProperty(propertyCollection, name, description, defaultValue, string.Empty);
        }

        /// <summary>
        /// Adds a custom property to a property collection
        /// </summary>
        /// <param name="propertyCollection">The property collection to add the property to</param>
        /// <param name="name">The name of the custom property to add</param>
        /// <param name="description">The description of the custom property to add</param>
        /// <param name="value">The value of the custom property to add</param>
        /// <param name="typeConverter">The type convertor to use for setting the property value</param>
        /// <returns>The added custom property object</returns>
        internal static IDTSCustomProperty100 AddCustomProperty(IDTSCustomPropertyCollection100 propertyCollection, string name, 
            string description, object value, string typeConverter)
        {
            IDTSCustomProperty100 property = propertyCollection.New();
            property.Name = name;
            property.Description = description;
            property.Value = value;
            property.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
            if (value is string)
            {
                property.State = DTSPersistState.PS_PERSISTASHEX;
            }
            if (!string.IsNullOrEmpty(typeConverter))
            {
                property.TypeConverter = typeConverter;
            }

            return property;
        }
        
        /// <summary>
        /// Sets up the column-to-validation;
        /// associates each column with a validation method based on its target value type
        /// </summary>
        private void SetPropertyValidationTable()
        {
            _propertyValidationTable.Add(DelayValidationPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IsUnicodePropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(ColumnNamesInFirstRowPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(TreatEmptyStringsAsNullPropName, ValidateBooleanProperty);

            _propertyValidationTable.Add(TrimWhitespacePropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IncludeErrorAuditRowsPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IncludeLogOutputPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IncludeValidationMessagesPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IncludeLogSourceDataPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IncludeErrorValidationMessagesPropName, ValidateBooleanProperty);
            _propertyValidationTable.Add(IncludeErrorSourceDataPropName, ValidateBooleanProperty);

            _propertyValidationTable.Add(HeaderRowDelimiterPropName, ValidateDelimiterProperty);
            _propertyValidationTable.Add(ColumnDelimiterPropName, ValidateDelimiterProperty);
            _propertyValidationTable.Add(RowDelimiterPropName, ValidateRowDelimiterProperty);
            _propertyValidationTable.Add(TextQualifierPropName, ValidateDelimiterProperty);

            _propertyValidationTable.Add(EscapeCharPropName, ValidateCharProperty);

            _propertyValidationTable.Add(LogSourceDataTypePropName, ValidateStringDataTypeProperty);
            _propertyValidationTable.Add(ErrorSourceDataTypePropName, ValidateStringDataTypeProperty);

            _propertyValidationTable.Add(ConfiguredFileColumnsPropName, ValidateConfiguredFileColumnsProperty);
        }

        /// <summary>
        /// Fires an error message to the PostErrorEvent
        /// </summary>
        /// <param name="errorMessage">The error message to fire</param>
        private void PostError(string errorMessage)
        {
            if (PostErrorEvent != null)
            {
                PostErrorEvent(errorMessage);
            }
        }
    }
}
